package model;

import domain.RawDataDO;
import model.param.CombineParam;
import model.param.GatewayParam;
import model.result.MediateResult;
import model.rsquare.CombineRSquare;
import model.rsquare.GatewayRSquare;
import org.apache.commons.math3.stat.regression.OLSMultipleLinearRegression;

import java.util.*;

/**
 * @author 刀氏春秋
 * @date 2023/5/16
 */
public class OlsAlgorithmUtil {
    /**
     * 求解三个浮点数最大值
     *
     * @param a 第一个参数
     * @param b 第二个参数
     * @param c 第三个参数
     * @return 最大数的参数序号，字符串版
     */
    private String maxThreeDouble(Double a, Double b, Double c) {
        if (a > b && a > c) return "1";
        else if (b > a && b > c) return "2";
        else return "3";
    }

    /**
     * 根据资产号查找DO对象
     *
     * @param assetNo 资产号
     * @param data    DO列表
     * @return DO对象
     */
    private RawDataDO getDataByAssetNO(String assetNo, List<RawDataDO> data) {
        for (RawDataDO item : data) {
            if (item.getAssetNo().equals(assetNo))
                return item;
        }
        return new RawDataDO();
    }

    /**
     * 三相式多元线性回归参数准备
     *
     * @param data 电流电压数据
     * @return Ols参数矩阵
     */
    private Map<String, GatewayParam> generateGatewayParameters(List<RawDataDO> data) {
        // 保存结果
        Map<String, GatewayParam> alParams = new HashMap<>();

        // 生成参数需要三块总表数据
        // 计算参数准备，表电压 = b1 * 总表A/B/C相电压 + b2 * 总表A/B/C相电流 + b3 * 表电流
        List<RawDataDO> gatewayData = data.subList(0, 4);
        // 对于每个DO，以assertNo标记其自己的数据
        for (RawDataDO item : data) {
            // 跳过总表
            if (item.getHplcPhase().equals("0")) continue;
            // 从item中获取数据并存入到Ols算法参数对象中
            // 实例化Ols算法对象
            GatewayParam param = new GatewayParam();
            param.setParams(gatewayData.get(0), gatewayData.get(1), gatewayData.get(2), item);
            // 一个电表对应一个算法对象，其保存相位分析所需全部数据

            alParams.put(item.getAssetNo(), param);
        }
        return alParams;
    }

    /**
     * 计算三相多元线性方程R方
     *
     * @param data 电流电压数据
     * @return 电表资产号与R方计算结果的Map
     */
    public Map<String, GatewayRSquare> getRSquareOfGateway(List<RawDataDO> data) {
        // 首先提取出总表电流-电压数据
        Map<String, GatewayParam> alParams = generateGatewayParameters(data);
        Map<String, GatewayRSquare> results = new HashMap<>();
        OLSMultipleLinearRegression calculateCore = new OLSMultipleLinearRegression();

        Set<String> meterSet = alParams.keySet();

        for (String meterNo : meterSet) {
            GatewayParam alParam = alParams.get(meterNo);

            // 从参数对象中获取数据
            double[][] sampleA = alParam.getX(1);
            double[][] sampleB = alParam.getX(2);
            double[][] sampleC = alParam.getX(3);
            double[] sampleY = alParam.getY();

            if (sampleA.length == 0) {
                GatewayRSquare result = new GatewayRSquare(-0.5, -0.5, -0.5, -100.0, -100.0, -100.0, sampleA.length);
                results.put(meterNo, result);
            } else {
                // 计算代入A相计算R方
                calculateCore.newSampleData(sampleY, sampleA);
                Double rSquareA = calculateCore.calculateRSquared();
                Double aResidual = calculateCore.calculateResidualSumOfSquares();
                // System.out.printf("b0 = %f, b1 = %f, b2 = %f, b3 = %f, R-square = %f", b[0], b[1], b[2], b[3], rSquareA);
                // 计算代入B相计算R方
                calculateCore.newSampleData(sampleY, sampleB);
                Double rSquareB = calculateCore.calculateRSquared();
                Double bResidual = calculateCore.calculateResidualSumOfSquares();
                // 计算代入C相计算R方
                calculateCore.newSampleData(sampleY, sampleC);
                Double rSquareC = calculateCore.calculateRSquared();
                Double cResidual = calculateCore.calculateResidualSumOfSquares();
                // 将结果保存
                GatewayRSquare result = new GatewayRSquare(rSquareA, rSquareB, rSquareC, aResidual, bResidual, cResidual, sampleA.length);
                results.put(meterNo, result);
            }
        }

        return results;
    }

    /**
     * 计算各表多元线性方程R方
     * 计算逻辑和总表有所不同，各表的方程组不同时代入模型计算，而是逐个计算
     *
     * @param data 单相表数据列表
     * @return 每个表同其他表拟合R方结果
     */
    public Map<String, CombineRSquare> getRSquareOfUserMeterCombination(List<RawDataDO> data) {
        Map<String, CombineRSquare> results = new TreeMap<>();
        OLSMultipleLinearRegression calculateCore = new OLSMultipleLinearRegression();
        // 对每一个电表，将其数据点列表转为算法参数包装类，再使用getter获取y列向量和x矩阵
        for (RawDataDO host : data) {
            // 排除三相表，以及总表，只看户表之间关系
            if (host.getWiringMode().equals("3")) continue;

            Map<String, Double> rSquare = new TreeMap<>();
            CombineRSquare result = new CombineRSquare(rSquare);
            // 获取算法参数并计算R方
            for (RawDataDO custom : data) {
                // 排除三相表
                if (custom.getWiringMode().equals("3")) {
                    continue;
                } else if (host.getAssetNo().equals(custom.getAssetNo())) {
                    // 若主从表相同，认定为同一个
                    result.addResult(custom.getAssetNo(), 1d);
                    continue;
                }
                CombineParam param = new CombineParam();
                param.setParams(host, custom);
                calculateCore.newSampleData(param.getY(), param.getX());
                result.addResult(custom.getAssetNo(), calculateCore.calculateRSquared());
            }
            // 得到的一组R方计算结果，将其保存在result中
            result.addResult("实际相位", Double.parseDouble(host.getHplcPhase()));
            results.put(host.getAssetNo(), result);
        }
        return results;
    }

    /**
     * 验证相位分析准确率
     *
     * @param rSquares R方分析结果
     * @param data     DO数据列表
     */
    public void phaseVerifyWithAnswer(Map<String, GatewayRSquare> rSquares, List<RawDataDO> data) {
        List<MediateResult> results = new LinkedList<>();
        float count = 0;
        for (String assetNo : rSquares.keySet()) {
            MediateResult result = new MediateResult();
            GatewayRSquare rSquare = rSquares.get(assetNo);

            // 判断相位识别结果并同hplc识别结果验证
            String verifyPhase = maxThreeDouble(rSquare.getAPhase(), rSquare.getBPhase(), rSquare.getCPhase());
            Boolean isSuccess = verifyPhase.equals(getDataByAssetNO(assetNo, data).getHplcPhase());
            if (isSuccess) {
                count++;
            } else {
                System.out.printf("Phase verify failure: [asset No. %s], [ACV: %s, PDV: %s], [R^2: %s, %s, %s]%n",
                        assetNo, getDataByAssetNO(assetNo, data).getHplcPhase(), verifyPhase,
                        rSquare.getAPhase(), rSquare.getBPhase(), rSquare.getCPhase()
                );
            }

            // 设定相位识别结果
            result.setVerifyResult(verifyPhase);
            result.setRealResult(getDataByAssetNO(assetNo, data).getHplcPhase());
            result.setAssetNo(assetNo);
            result.setIsSuccess(isSuccess);
            result.setRSquareA(rSquare.getAPhase());
            result.setRSquareB(rSquare.getBPhase());
            result.setRSquareC(rSquare.getCPhase());
            result.setResidualA(rSquare.getAResidual());
            result.setResidualB(rSquare.getBResidual());
            result.setResidualC(rSquare.getCResidual());
            result.setPoints(rSquare.getPoints());

            results.add(result);
        }
        // 打印识别成功率（参考hplc）
        System.out.printf("Success rate: %f\n", (count / results.size()) * 100.0);
        // 输出为excel表格
        ExportExcelUtil<MediateResult> exporter = new ExportExcelUtil<>();
        exporter.writeRawDataToExcel("src/main/resources/verify-result.xlsx", results, MediateResult.class);
    }

    public void phaseGroup(Map<String, CombineRSquare> rSquares) {
        // 获取到资产号列表
        Set<String> assetNos = rSquares.keySet();
        ExportExcelUtil<String> exporter = new ExportExcelUtil<>();
        // 将数据转为二维数组列表
        List<List<Object>> table = new LinkedList<>();
        for (String assetNo : assetNos) {
            // 对应一行数据列表，第一个数据是电表资产号
            List<Object> tuple = new LinkedList<>();
            tuple.add(assetNo);
            // 获取R方组合数据
            CombineRSquare rSquare = rSquares.get(assetNo);
            tuple.addAll(rSquare.getRSquareResult());
            // 将数据添加到表中
            table.add(tuple);
        }

        // 开始导出数据
        exporter.writeDynamicsDataToExcel("src/main/resources/combine-result.xlsx", table, assetNos.stream().toList());
    }
}
